Down stream analysis of the dataset ‘3_IMR90_206plex’

setup

library(Seurat)
library(ggforce)
library(tidyverse)
dcolor <- ggsci::scale_color_d3('category20')
ccolor <- viridis::scale_color_viridis()
style <- function(x,color,method='UMAP',axis=c(1,2),Theme=NULL,
                  axis.text=FALSE,axis.title=FALSE,coord_fix =TRUE,
                  palette=TRUE,title=NULL,
                  legend=TRUE,scale_color_log10=FALSE,
                  guide_pointSize=2.5,...) {
  axis <- paste0(method,axis)
  if(is.null(Theme)) Theme <- theme_bw
  g <- ggplot() + Theme()
  
  if(coord_fix) {
    g <- g + coord_fixed()
  }
  if(!axis.text) {
    g <- g + theme(axis.text = element_blank(),axis.ticks = element_blank())
  }
  if(!axis.title) {
    g <- g + theme(axis.title = element_blank())
  }
  if(is.null(title)) {
    g <- g + ggtitle(method) 
  }else if(!is.na(title)) {
    g <- g + ggtitle(title) 
  }
  if(!legend) g <- g + theme(legend.position = 'none')
  
  if(scale_type(as.matrix(x[,color]))=='discrete' & !is.null(guide_pointSize)) {
    g <- g + guides(color = guide_legend(override.aes = list(size=guide_pointSize)))
  }
  
  if(palette) {
    if(scale_type(as.matrix(x[,color]))=='discrete') {
      g <- g + ggsci::scale_color_d3('category20')
    }else{
      if(scale_color_log10){
        g <- g + viridis::scale_color_viridis(trans='log10')
      }else{
        g <- g + viridis::scale_color_viridis()
      }
    }
  }
  return(g)
}

load data

data <- tibble(
  path = list.files('3_IMR90_206plex/Raw/',full.names = T),
  sampleName = c('d0-r1', 'd0-r2', 'd3-r1', 'd3-r2', 'd6-r1', 'd6-r2')
  ) %>%
  separate(sampleName,into=c('day','rep'),remove = F) %>%
  mutate(
    metric = map(path,~{
      .x %>%
        read_csv(show_col_types=F) %>% 
        unite(cell,tileID,cellID,sep = '-',remove = F)
      }),
    IF = map(metric,~{.x[,-(2:8)]}),
    meta = map2(metric,IF,~{
      .x[,1:8] %>%
        unite(cell,tileID,cellID,sep = '-',remove = F) %>%
        mutate(totalExp = rowSums(.y[,-(1:3)]))
      }),
    ) %>%
  select(-path,-metric)

Quality check & control

data %>%
  select(-IF) %>% unnest(meta) %>%
  ggplot(aes(x,y,color=day)) +
    theme_void() +
    geom_point(size=.5) + dcolor +
    facet_grid(rep~day) + coord_fixed()

data %>%
  select(sampleName,meta) %>% unnest(meta) %>%
  ggplot(aes(sampleName,totalExp)) + geom_violin() + geom_boxplot()

thrsl = c(7,7,5)
names(thrsl) <- c('d0','d3','d6')

dataf <- data %>%
  mutate(
    idx = map2(meta,day,~{.x$totalExp > thrsl[.y]}),
    IF = map2(IF,idx,~{.x[.y,]}),
    meta = map2(meta,idx,~{.x[.y,]})
    ) %>%
  mutate(
    idx2 = map(IF,~{rowSums(.x[,-1]==0) == 0}),
    IF = map2(IF,idx2,~{.x[.y,]}),
    meta = map2(meta,idx2,~{.x[.y,]}),
    nCell = map(idx2,sum) %>% unlist()
    ) %>%
  select(-idx,-idx2)

dataf %>%
  select(sampleName,meta) %>% unnest(meta) %>%
  ggplot(aes(sampleName,totalExp)) + 
  geom_violin() + geom_boxplot()

dataf %>%  
  ggplot(aes(sampleName,nCell)) + 
  theme_classic() +
  geom_bar(stat='identity') +
  scale_x_discrete(limits=rev) +
  theme(axis.title.y = element_blank()) +  
  coord_flip() + theme(aspect.ratio = .35) 

tmp <- dataf %>% select(sampleName,IF) %>% unnest(IF)
idx <- with(tmp,model.matrix(~0+sampleName))
colnames(idx) <- sub("sampleName","",colnames(idx))
avgmat <- tmp[,-(1:2)] %>% as.matrix() %>% t %>% {t(.%*%idx)/colSums(idx)} %>% scale %>% asinh %>% t 
pheatmap::pheatmap(avgmat,cluster_cols = F,cellwidth = 10,color=viridis::viridis(10),show_rownames = F)

Meta <- dataf %>%
  select(sampleName,day,rep,meta) %>% unnest(meta) %>%
  unite(Cell,day,rep,cell,sep = '-',remove = F)
X <- bind_rows(dataf$IF) %>% select(-cell) %>% 
   scale() %>% asinh()
rownames(X) <- Meta$Cell
p <- prcomp(X)
screeplot(p,type='l',n=30)

total <- bind_rows(dataf$meta) %>% pull(totalExp) 
cor(p$x[,1:30],total) %>%
  plot(xlab = 'PC',ylab = 'CorCoef_vsTotalExp')

um <- uwot::umap(p$x[,2:20],
                 metric = 'cosine',
                 n_neighbors = 30,
                 min_dist = .3) %>%
  as_tibble(rownames = 'Cell') %>%
  dplyr::rename(UMAP1=2,UMAP2=3)
Metaum <- inner_join(Meta,um,by = 'Cell')
ums <- list(
  style(Metaum,'day',title = NULL,size = .25,alpha = .6),
  style(Metaum,'rep',title = NA,size = .25,alpha = .6),
  style(Metaum,'totalExp',title = NA,size = .25,alpha = .6)
  ) %>%
  patchwork::wrap_plots(ncol = 3)
ums

markers <- c('H3.1','LMNB1','IL-6','p21','pSTAT3')
markers_exp <- X %>%
  as_tibble(rownames = 'Cell') %>%
  select(Cell,all_of(markers)) %>%
  gather(key=SYMBOL,value=ExpLevel,-Cell)
a1 <- Metaum %>%
  inner_join(markers_exp,by = 'Cell') %>%
  style('ExpLevel',size=.2,title = "") + 
    theme_bw() + facet_wrap(~SYMBOL,nrow=1) +
    theme(axis.ticks = element_blank(),
          axis.text = element_blank()) +
    labs(color='Expression Level')
a2 <- Metaum %>%
  inner_join(markers_exp,by='Cell') %>%
  ggplot(aes(sampleName,ExpLevel,fill=day)) +
    theme_bw() +
    geom_violin() + 
    geom_boxplot(width=.2,fill='white',outlier.color = NA) +
    facet_wrap(~SYMBOL,nrow=1,scales='free') +
    theme(axis.ticks.x = element_blank(),
          axis.text.x = element_blank()) +
    dfill + labs(y='Expression Level')
(a1/a2) + patchwork::plot_layout(guides = 'collect',heights = c(1.3,1))

Pseudo time

Senes <- c("gH2AX","HMGA1","Ki67","IL-1a","IL-6","IL-8","LMNB1","p21","p53BP1","PML")
Senes_exp <- X %>%
  as_tibble(rownames = 'Cell') %>%
  select(Cell,all_of(Senes)) %>%
  gather(key=SYMBOL,value=ExpLevel,-Cell)
Metaum %>%
  inner_join(Senes_exp,by='Cell') %>%
  ggplot(aes(sampleName,ExpLevel,fill=day)) +
    theme_bw() +
    geom_violin() + 
    geom_boxplot(width=.2,fill='white',outlier.color = NA) +
    facet_wrap(~SYMBOL,scales='free_y') +
    theme(axis.ticks.x = element_blank(),
          axis.text.x = element_blank()) +
    dfill + labs(y='Expression Level')

p2 <- prcomp(X[,Senes])
screeplot(p2,type='l',n=30)

ph <- p2$x[,-1] %>% 
  phateR::phate(mds.dist.method = "cosine",
                knn = 30)
Calculating PHATE...
  Running PHATE on 6703 observations and 9 variables.
  Calculating graph and diffusion operator...
    Calculating KNN search...
    Calculated KNN search in 0.55 seconds.
    Calculating affinities...
    Calculated affinities in 0.04 seconds.
  Calculated graph and diffusion operator in 0.61 seconds.
  Calculating landmark operator...
    Calculating SVD...
    Calculated SVD in 0.68 seconds.
    Calculating KMeans...
    Calculated KMeans in 2.08 seconds.
  Calculated landmark operator in 3.16 seconds.
  Calculating optimal t...
    Automatically selected t = 20
  Calculated optimal t in 2.44 seconds.
  Calculating diffusion potential...
  Calculated diffusion potential in 0.57 seconds.
  Calculating metric MDS...
  Calculated metric MDS in 4.99 seconds.
Calculated PHATE in 11.77 seconds.
Metaum <- Metaum %>%
  mutate(PHATE1=ph$embedding[,1],PHATE2=ph$embedding[,2])

style(Metaum,"day",method = "PHATE",size=.5)

style(Metaum,"totalExp",method = "PHATE",size=.5)

set.seed(1); clu <- ph$embedding %>% kmeans(centers = 5)
ph$embedding %>%
  as_tibble() %>%
  mutate(clu=as.character(clu$cluster)) %>%
  style("clu","PHATE",palette = F,size=.3)

library(slingshot)
sce <- getLineages(ph$embedding, clu$cluster, start.clus = '3')
Warning: useNames = NA is deprecated. Instead, specify either useNames = TRUE or useNames = TRUE.Warning: useNames = NA is deprecated. Instead, specify either useNames = TRUE or useNames = TRUE.Warning: useNames = NA is deprecated. Instead, specify either useNames = TRUE or useNames = TRUE.Warning: useNames = NA is deprecated. Instead, specify either useNames = TRUE or useNames = TRUE.Warning: useNames = NA is deprecated. Instead, specify either useNames = TRUE or useNames = TRUE.
sce <- getCurves(sce)
sce
class: PseudotimeOrdering 
dim: 6703 1 
metadata(4): lineages mst slingParams curves
pathStats(2): pseudotime weights
cellnames(6703): d0-r1-1-3 d0-r1-1-4 ... d6-r2-100-2 d6-r2-100-3
cellData names(2): reducedDim clusterLabels
pathnames(1): Lineage1
pathData names(0):
PT <- slingPseudotime(sce)[,1]
PT <- (PT-min(PT))/(max(PT)-min(PT))
Metaum <- Metaum %>% mutate(PseudoTime=PT)
Metaum
{style(Metaum,'PseudoTime',"PHATE",size=.5)/style(Metaum,'PseudoTime',"UMAP",size=.5)} + patchwork::plot_layout(guides = 'collect')|
  {style(Metaum,'day',"PHATE",size=.5)/style(Metaum,'day',"UMAP",size=.5)} + patchwork::plot_layout(guides = 'collect')

Metaum <- Metaum %>%
  arrange(PseudoTime) %>%
  mutate(
    dPseudoTime = (floor(PseudoTime*10)+.5)/10,
    dPseudoTime = ifelse(dPseudoTime==1.05,.95,dPseudoTime),
    dPseudoTime = as.factor(dPseudoTime)
    )

Metaum %>%
  ggplot(aes(PseudoTime,fill=day)) +
  theme_bw() +
  geom_histogram(position = "dodge") + dfill

Metaum %>%
  dplyr::count(dPseudoTime,day) %>%
  ggplot(aes(dPseudoTime,n,fill=day)) + geom_bar(stat = "identity") + dfill +
  theme_bw() +
  labs(y="count")

a <- as_tibble(X,rownames="Cell") %>%
  gather(key=Prot,value=ExpLevel,-Cell) %>%
  left_join(Metaum,by="Cell")

Newdata <- seq(0.2,0.8,by=0.01)
Span <- 0.3

use_p1 <- c('HMGA1','Ki67','LMNB1','p21','p53BP1','PML')
LOESS1 <- a %>%
  filter(Prot %in% use_p1) %>%
  select(Prot,ExpLevel,PseudoTime) %>%
  group_by(Prot) %>% nest() %>%
  mutate(LOESS = map(data,~loess(ExpLevel~PseudoTime,.x,span = Span)),
         PRED = map(LOESS,~predict(.x,newdata=Newdata)),
         newdata = map(PRED,~tibble(PseudoTime=Newdata,PRED=.x)) )

loessPl <- function(x,ratio = .35){
  ggplot(x,aes(PseudoTime,scaled,color=Prot)) + 
  geom_line() + theme_bw() +
  theme(legend.position = 'none') +
  theme(axis.ticks.y = element_blank(),
        strip.background = element_rect(fill=NA,color=NA), 
        panel.grid.major.x = element_blank(),panel.grid.minor = element_blank()) + 
  coord_fixed(ratio=ratio) + labs(y = 'scaled expression level')
}

LOESS1_scaled <- LOESS010f %>% select(Prot,newdata) %>% 
  mutate(newdata = map(newdata,~mutate(.x,scaled = (PRED-min(PRED))/(max(PRED)-min(PRED)) ))) %>%
  unnest(newdata)  %>% ungroup()
loessPl(LOESS1_scaled)

loessPl(LOESS1_scaled) + facet_wrap(~Prot)

use_p2 <- c("bCatenin","BCL2L1","Frizzled7","NOTCH1","p53BP1","pAKT","pATF2","pATM","pSMAD1/5","pSMAD3","pSRC")

LOESS2 <- a %>%
  filter(Prot %in% use_p2) %>%
  select(Prot,ExpLevel,PseudoTime) %>%
  group_by(Prot) %>% nest() %>%
  mutate(LOESS = map(data,~loess(ExpLevel~PseudoTime,.x,span = Span)),
         PRED = map(LOESS,~predict(.x,newdata=Newdata)),
         newdata = map(PRED,~tibble(PseudoTime=Newdata,PRED=.x)) )
LOESS2_scaled <- LOESS2 %>% select(Prot,newdata) %>% 
  mutate(newdata = map(newdata,~mutate(.x,scaled = (PRED-min(PRED))/(max(PRED)-min(PRED)) ))) %>%
  unnest(newdata)  %>% ungroup()
loessPl(LOESS2_scaled)

loessPl(LOESS2_scaled) + facet_wrap(~Prot)

LOESS3 <- a %>%
  filter(!Prot %in% use_p1,
         !Prot %in% use_p2) %>%
  select(Prot,ExpLevel,PseudoTime) %>%
  group_by(Prot) %>% nest() %>%
  mutate(LOESS = map(data,~loess(ExpLevel~PseudoTime,.x,span = Span)),
         PRED = map(LOESS,~predict(.x,newdata=Newdata)),
         newdata = map(PRED,~tibble(PseudoTime=Newdata,PRED=.x)) )
LOESS3_scaled <- LOESS3 %>% select(Prot,newdata) %>% 
  mutate(newdata = map(newdata,~mutate(.x,scaled = (PRED-min(PRED))/(max(PRED)-min(PRED)) ))) %>%
  unnest(newdata)  %>% ungroup()
id <- unique(LOESS3_scaled$Prot)
LOESS3_scaled %>%
  filter(Prot %in% id[1:48]) %>%
  loessPl(ratio = .2) + facet_wrap(~Prot,nrow = 6) +
  theme(axis.text = element_blank(),axis.ticks.x = element_blank())

LOESS3_scaled %>%
  filter(Prot %in% id[49:96]) %>%
  loessPl(ratio = .2) + facet_wrap(~Prot,nrow = 6) +
  theme(axis.text = element_blank(),axis.ticks.x = element_blank())

LOESS3_scaled %>%
  filter(Prot %in% id[97:144]) %>%
  loessPl(ratio = .2) + facet_wrap(~Prot,nrow = 6) + 
  theme(axis.text = element_blank(),axis.ticks.x = element_blank())

LOESS3_scaled %>%
  filter(Prot %in% id[145:190]) %>%
  loessPl(ratio = .3) + facet_wrap(~Prot,nrow = 6) +
  theme(axis.text = element_blank(),axis.ticks.x = element_blank())

Spatial

spatial corrrelation

spaCor <- function (x, coord, scaled = FALSE, R = 999){
  x <- asinh(scale(x))
  pos.dist <- dist(x = coord)
  pos.dist.mat <- as.matrix(x = pos.dist)
  w <- 1/pos.dist.mat^2
  diag(x = w) <- 0
  w <- w/sum(w)
  X <- t(w) %*% scale(x)
  t(scale(x))%*%X
}

Spas <- dataf %>%
  select(sampleName,day,rep,meta,IF) %>%
  mutate(
    IF = map2(IF,sampleName,~{
      .x %>% 
        mutate(cell = paste(.y,cell,sep='-')) %>%
        tib2df() %>% as.matrix() }),
    meta = map2(meta,sampleName,~{
      .x %>%
        mutate(Cell = paste(.y,cell,sep='-')) %>% 
        select(Cell,x,y) %>% tib2df() %>% as.matrix()}),
    spatialCorMat = map2(IF,meta,~spaCor(.x,.y,scaled = T)),
    spatialCorList = map(spatialCorMat,~{
      .x[!upper.tri(.x)] <- NA
      as_tibble(.x,rownames = 'A') %>%
        gather(B,spatialCor,-A) %>% na.omit()
      }),
    canonicalCorMat = map2(IF,meta,~cor(.x)),
    canonicalCorList = map(canonicalCorMat,~{
      .x[!upper.tri(.x)] <- NA
      as_tibble(.x,rownames = 'A') %>%
        gather(B,canonicalCor,-A) %>% na.omit()
      })
    ) %>%
  select(-IF,-meta) %>%
  mutate(dat = map2(spatialCorList,canonicalCorList,~{
    inner_join(.x,.y,by=c('A','B'))
  })) 
Spas_ <- Spas %>%
  select(-spatialCorMat) %>%
  unnest(spatialCorList) %>%
  ungroup() %>%
  unite(lab,A,B,sep=':')
use <- c("NOTCH1:pATM","p21:NOTCH1","p21:pATM")
Spas_ %>% filter(lab %in% use) %>%
  group_by(day,lab ) %>% summarise(spatialCor = mean(spatialCor)) %>%
  ungroup() %>%
  ggplot(aes(day,spatialCor,color=lab,group=lab)) + 
  theme_bw() +
  geom_point(size=2) + 
  geom_line() +
  geom_hline(yintercept = 0,linetype='dashed') +
  theme(panel.grid.major.x = element_blank(),
        panel.grid.minor.x = element_blank(),
        axis.title.x = element_blank()) +
  labs(color = 'Pair',y = 'spatial correlation coef.')

spatial distribution of expression

smoothTile <- function(Coord,X,N=3,grid=50,bw=2*sqrt(2),df=FALSE,center_weight=3) {
  use <- Coord %>% as_tibble() %>% mutate(exp = X)
  template <- expand_grid(
    tibble(x = seq(1:grid)),
    tibble(y = seq(1:grid))
    )
  D <- as.matrix(dist(template))
  D <- D<=bw
  tmp <- use %>%
    mutate(
      x = floor(grid*x/20480),
      y = floor(grid*y/20480)
      ) %>%
    group_by(x,y) %>%
    summarise(exp=mean(exp)) %>% ungroup() %>%
    left_join(template,.,by=c("x","y"))
  if(df){
    return(tmp)
  }else{
    for (j in 1:N) {
      tmp$exp <- lapply(1:nrow(template), function(x){
        if(!is.na(tmp$exp[x])) tmp$exp[x] <- center_weight*tmp$exp[x]
        idx <- which(D[x,])
        exps <- tmp$exp[idx] %>% na.omit
        if(!is.na(tmp$exp[x])) {
          res <- sum(exps)/(length(exps)+center_weight-1)
        }else{
          res <- mean(exps)
        }
        return(res)
        }) %>%
        unlist()
      tmp$exp <- ifelse(is.na(tmp$exp),min(tmp$exp,na.rm = T),tmp$exp)
    }
  return(tmp)
  }
}

exps1 <- X[grep('d3-r1',rownames(X)),]
coord1 <- Metaum %>% filter(day=='d3',rep=='r1') %>% select(Cell,x,y) %>% tib2df()
p21 <- smoothTile(coord1,exps1[rownames(coord1),'p21'],N = 3,center_weight = 3,bw = 2*sqrt(2),grid = 50)
patm <- smoothTile(coord1,exps1[rownames(coord1),'pATM'],N = 3,center_weight = 3,bw = 2*sqrt(2),grid = 50)
notch <- smoothTile(coord1,exps1[rownames(coord1),'NOTCH1'],N = 3,center_weight = 3,bw = 2*sqrt(2),grid = 50)  
p21patm <- inner_join(
  p21 %>% dplyr::rename(p21 = exp),
  patm %>% dplyr::rename(pATM = exp),
  by = c("x","y")
  ) %>%
  mutate(
    p21 = (p21-min(p21))/max(p21-min(p21)),
    pATM = (pATM-min(pATM))/max(pATM-min(pATM)),
    log2FC = log2(p21/pATM)
    )

p21_ <- p21 %>%
  ggplot(aes(x,y,fill = scale(exp))) + geom_tile() + coord_fixed() + 
  scale_fill_gradient2(low='white',mid='white',high='green') +
  theme_void() + labs(fill='p21')
patm_ <- patm %>%
  ggplot(aes(x,y,fill = scale(exp))) + geom_tile() + coord_fixed() + 
  scale_fill_gradient2(low='white',mid='white',high='blue')+
  theme_void() + labs(fill='pATM')
p21patm_ <- p21patm %>%
  mutate(
    log2FC = ifelse(log2FC < -2.5,-2.5,log2FC),
    log2FC = ifelse(log2FC > 2.5,2.5,log2FC)) %>%
  ggplot(aes(x,y,fill = log2FC)) + geom_tile() + coord_fixed() + 
  scale_fill_gradient2(low='blue',mid='white',high='green',midpoint = 0) +
  theme_void()
{{p21_/patm_}|p21patm_} + patchwork::plot_layout(guides='collect') & theme(legend.position = 'bottom')

p21notch <- inner_join(
  notch %>% dplyr::rename(NOTCH1 = exp),
  patm %>% dplyr::rename(pATM = exp),
  by = c("x","y")
  ) %>%
  mutate(
    NOTCH1 = (NOTCH1-min(NOTCH1))/max(NOTCH1-min(NOTCH1)),
    pATM = (pATM-min(pATM))/max(pATM-min(pATM)),
    log2FC = log2(NOTCH1/pATM)
    )

notch_ <- notch %>%
  ggplot(aes(x,y,fill = scale(exp))) + geom_tile() + coord_fixed() + 
  scale_fill_gradient2(low='white',mid='white',high='red') +
  theme_void() + 
  labs(fill='NOTCH1')
p21notch_ <- p21notch %>%
  mutate(
    log2FC = ifelse(log2FC < -2.5,-2.5,log2FC),
    log2FC = ifelse(log2FC > 2.5,2.5,log2FC)) %>%
  ggplot(aes(x,y,fill = log2FC)) + geom_tile() + coord_fixed() + scale_fill_gradient2(low='blue',mid='white',high='red',midpoint = 0) + theme_void()
{{notch_/patm_}|p21notch_} + patchwork::plot_layout(guides='collect') & theme(legend.position = 'bottom')

sessionInfo()
R version 4.2.2 (2022-10-31)
Platform: aarch64-apple-darwin20 (64-bit)
Running under: macOS Ventura 13.2.1

Matrix products: default
LAPACK: /Library/Frameworks/R.framework/Versions/4.2-arm64/Resources/lib/libRlapack.dylib

locale:
[1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8

attached base packages:
[1] stats4    stats     graphics  grDevices utils     datasets  methods   base     

other attached packages:
 [1] slingshot_2.6.0             TrajectoryUtils_1.6.0       SingleCellExperiment_1.20.1 SummarizedExperiment_1.28.0 Biobase_2.58.0              GenomicRanges_1.50.2        GenomeInfoDb_1.34.9        
 [8] IRanges_2.32.0              S4Vectors_0.36.2            BiocGenerics_0.44.0         MatrixGenerics_1.10.0       matrixStats_1.0.0           princurve_2.1.6             lubridate_1.9.2            
[15] forcats_1.0.0               stringr_1.5.0               dplyr_1.1.2                 purrr_1.0.1                 readr_2.1.4                 tidyr_1.3.0                 tibble_3.2.1               
[22] tidyverse_2.0.0             ggforce_0.4.1               ggplot2_3.4.2               SeuratObject_4.1.3          Seurat_4.3.0.1             

loaded via a namespace (and not attached):
  [1] utf8_1.2.3                spatstat.explore_3.2-1    reticulate_1.30           tidyselect_1.2.0          RSQLite_2.3.1             AnnotationDbi_1.60.2      htmlwidgets_1.6.2        
  [8] grid_4.2.2                Rtsne_0.16                munsell_0.5.0             codetools_0.2-19          ica_1.0-3                 future_1.33.0             miniUI_0.1.1.1           
 [15] withr_2.5.0               spatstat.random_3.1-5     colorspace_2.1-0          progressr_0.13.0          knitr_1.43                rstudioapi_0.15.0         ROCR_1.0-11              
 [22] tensor_1.5                listenv_0.9.0             labeling_0.4.2            GenomeInfoDbData_1.2.9    polyclip_1.10-4           bit64_4.0.5               farver_2.1.1             
 [29] pheatmap_1.0.12           rprojroot_2.0.3           parallelly_1.36.0         vctrs_0.6.3               generics_0.1.3            xfun_0.39                 timechange_0.2.0         
 [36] R6_2.5.1                  phateR_1.0.7              isoband_0.2.7             bitops_1.0-7              spatstat.utils_3.0-3      cachem_1.0.8              DelayedArray_0.24.0      
 [43] promises_1.2.0.1          scales_1.2.1              vroom_1.6.3               gtable_0.3.3              globals_0.16.2            goftest_1.2-3             tidygraph_1.2.3          
 [50] rlang_1.1.1               pamr_1.56.1               splines_4.2.2             lazyeval_0.2.2            spatstat.geom_3.2-4       yaml_2.3.7                reshape2_1.4.4           
 [57] abind_1.4-5               stylo_0.7.4               httpuv_1.6.11             tools_4.2.2               tcltk_4.2.2               ellipsis_0.3.2            RColorBrewer_1.1-3       
 [64] proxy_0.4-27              ggridges_0.5.4            Rcpp_1.0.11               plyr_1.8.8                sparseMatrixStats_1.10.0  zlibbioc_1.44.0           RCurl_1.98-1.12          
 [71] deldir_1.0-9              pbapply_1.7-2             viridis_0.6.4             cowplot_1.1.1             zoo_1.8-12                ggrepel_0.9.3             cluster_2.1.4            
 [78] here_1.0.1                magrittr_2.0.3            data.table_1.14.8         scattermore_1.2           lmtest_0.9-40             RANN_2.6.1                fitdistrplus_1.1-11      
 [85] hms_1.1.3                 patchwork_1.1.2           mime_0.12                 evaluate_0.21             xtable_1.8-4              gridExtra_2.3             compiler_4.2.2           
 [92] KernSmooth_2.23-22        crayon_1.5.2              htmltools_0.5.5           mgcv_1.9-0                later_1.3.1               tzdb_0.4.0                DBI_1.1.3                
 [99] tweenr_2.0.2              MASS_7.3-60               Matrix_1.5-4.1            cli_3.6.1                 parallel_4.2.2            igraph_1.5.0.1            pkgconfig_2.0.3          
[106] sp_2.0-0                  plotly_4.10.2             spatstat.sparse_3.0-2     XVector_0.38.0            digest_0.6.33             sctransform_0.3.5         RcppAnnoy_0.0.21         
[113] tsne_0.1-3.1              spatstat.data_3.0-1       Biostrings_2.66.0         rmarkdown_2.23            leiden_0.4.3              uwot_0.1.16               DelayedMatrixStats_1.20.0
[120] shiny_1.7.4.1             lifecycle_1.0.3           nlme_3.1-162              jsonlite_1.8.7            viridisLite_0.4.2         fansi_1.0.4               pillar_1.9.0             
[127] ggsci_3.0.0               lattice_0.21-8            KEGGREST_1.38.0           fastmap_1.1.1             httr_1.4.6                survival_3.5-5            glue_1.6.2               
[134] png_0.1-8                 bit_4.0.5                 tcltk2_1.2-11             class_7.3-22              stringi_1.7.12            blob_1.2.4                memoise_2.0.1            
[141] irlba_2.3.5.1             e1071_1.7-13              future.apply_1.11.0       ape_5.7-1                
LS0tCnRpdGxlOiAiM19JTVI5MF8yMDZwbGV4X2FuYWx5c2lzIgpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKLS0tCgpEb3duIHN0cmVhbSBhbmFseXNpcyBvZiB0aGUgZGF0YXNldCAnM19JTVI5MF8yMDZwbGV4JwoKIyMgc2V0dXAKCmBgYHtyIHNldHVwfQpsaWJyYXJ5KFNldXJhdCkKbGlicmFyeShnZ2ZvcmNlKQpsaWJyYXJ5KHRpZHl2ZXJzZSkKYGBgCgpgYGB7cn0KZGNvbG9yIDwtIGdnc2NpOjpzY2FsZV9jb2xvcl9kMygnY2F0ZWdvcnkyMCcpCmRmaWxsIDwtIGdnc2NpOjpzY2FsZV9maWxsX2QzKCdjYXRlZ29yeTIwJykKY2NvbG9yIDwtIHZpcmlkaXM6OnNjYWxlX2NvbG9yX3ZpcmlkaXMoKQpzdHlsZSA8LSBmdW5jdGlvbih4LGNvbG9yLG1ldGhvZD0nVU1BUCcsYXhpcz1jKDEsMiksVGhlbWU9TlVMTCwKICAgICAgICAgICAgICAgICAgYXhpcy50ZXh0PUZBTFNFLGF4aXMudGl0bGU9RkFMU0UsY29vcmRfZml4ID1UUlVFLAogICAgICAgICAgICAgICAgICBwYWxldHRlPVRSVUUsdGl0bGU9TlVMTCwKICAgICAgICAgICAgICAgICAgbGVnZW5kPVRSVUUsc2NhbGVfY29sb3JfbG9nMTA9RkFMU0UsCiAgICAgICAgICAgICAgICAgIGd1aWRlX3BvaW50U2l6ZT0yLjUsLi4uKSB7CiAgYXhpcyA8LSBwYXN0ZTAobWV0aG9kLGF4aXMpCiAgaWYoaXMubnVsbChUaGVtZSkpIFRoZW1lIDwtIHRoZW1lX2J3CiAgZyA8LSBnZ3Bsb3QoKSArIFRoZW1lKCkKICBnIDwtIGcgKyBnZW9tX3BvaW50KGFlc18oeD1hcy5uYW1lKGF4aXNbMV0pLHk9YXMubmFtZShheGlzWzJdKSxjb2xvcj1hcy5uYW1lKGNvbG9yKSkseCwuLi4pCiAgCiAgaWYoY29vcmRfZml4KSB7CiAgICBnIDwtIGcgKyBjb29yZF9maXhlZCgpCiAgfQogIGlmKCFheGlzLnRleHQpIHsKICAgIGcgPC0gZyArIHRoZW1lKGF4aXMudGV4dCA9IGVsZW1lbnRfYmxhbmsoKSxheGlzLnRpY2tzID0gZWxlbWVudF9ibGFuaygpKQogIH0KICBpZighYXhpcy50aXRsZSkgewogICAgZyA8LSBnICsgdGhlbWUoYXhpcy50aXRsZSA9IGVsZW1lbnRfYmxhbmsoKSkKICB9CiAgaWYoaXMubnVsbCh0aXRsZSkpIHsKICAgIGcgPC0gZyArIGdndGl0bGUobWV0aG9kKSAKICB9ZWxzZSBpZighaXMubmEodGl0bGUpKSB7CiAgICBnIDwtIGcgKyBnZ3RpdGxlKHRpdGxlKSAKICB9CiAgaWYoIWxlZ2VuZCkgZyA8LSBnICsgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gJ25vbmUnKQogIAogIGlmKHNjYWxlX3R5cGUoYXMubWF0cml4KHhbLGNvbG9yXSkpPT0nZGlzY3JldGUnICYgIWlzLm51bGwoZ3VpZGVfcG9pbnRTaXplKSkgewogICAgZyA8LSBnICsgZ3VpZGVzKGNvbG9yID0gZ3VpZGVfbGVnZW5kKG92ZXJyaWRlLmFlcyA9IGxpc3Qoc2l6ZT1ndWlkZV9wb2ludFNpemUpKSkKICB9CiAgCiAgaWYocGFsZXR0ZSkgewogICAgaWYoc2NhbGVfdHlwZShhcy5tYXRyaXgoeFssY29sb3JdKSk9PSdkaXNjcmV0ZScpIHsKICAgICAgZyA8LSBnICsgZ2dzY2k6OnNjYWxlX2NvbG9yX2QzKCdjYXRlZ29yeTIwJykKICAgIH1lbHNlewogICAgICBpZihzY2FsZV9jb2xvcl9sb2cxMCl7CiAgICAgICAgZyA8LSBnICsgdmlyaWRpczo6c2NhbGVfY29sb3JfdmlyaWRpcyh0cmFucz0nbG9nMTAnKQogICAgICB9ZWxzZXsKICAgICAgICBnIDwtIGcgKyB2aXJpZGlzOjpzY2FsZV9jb2xvcl92aXJpZGlzKCkKICAgICAgfQogICAgfQogIH0KICByZXR1cm4oZykKfQp0aWIyZGYgPC0gZnVuY3Rpb24odGliLFJvd05hbWVzPTEpIHsKICBybiA8LSBwdWxsKHRpYixhbGxfb2YoUm93TmFtZXMpKQogIGRmIDwtIHRpYiAlPiUKICAgIHNlbGVjdCgtUm93TmFtZXMpICU+JQogICAgYXMuZGF0YS5mcmFtZSgpCiAgcm93bmFtZXMoZGYpIDwtIHJuCiAgcmV0dXJuKGRmKQp9CmBgYAoKCiMjICBsb2FkIGRhdGEKCmBgYHtyfQpkYXRhIDwtIHRpYmJsZSgKICBwYXRoID0gbGlzdC5maWxlcygnM19JTVI5MF8yMDZwbGV4L1Jhdy8nLGZ1bGwubmFtZXMgPSBUKSwKICBzYW1wbGVOYW1lID0gYygnZDAtcjEnLCAnZDAtcjInLCAnZDMtcjEnLCAnZDMtcjInLCAnZDYtcjEnLCAnZDYtcjInKQogICkgJT4lCiAgc2VwYXJhdGUoc2FtcGxlTmFtZSxpbnRvPWMoJ2RheScsJ3JlcCcpLHJlbW92ZSA9IEYpICU+JQogIG11dGF0ZSgKICAgIG1ldHJpYyA9IG1hcChwYXRoLH57CiAgICAgIC54ICU+JQogICAgICAgIHJlYWRfY3N2KHNob3dfY29sX3R5cGVzPUYpICU+JSAKICAgICAgICB1bml0ZShjZWxsLHRpbGVJRCxjZWxsSUQsc2VwID0gJy0nLHJlbW92ZSA9IEYpCiAgICAgIH0pLAogICAgSUYgPSBtYXAobWV0cmljLH57LnhbLC0oMjo4KV19KSwKICAgIG1ldGEgPSBtYXAyKG1ldHJpYyxJRix+ewogICAgICAueFssMTo4XSAlPiUKICAgICAgICB1bml0ZShjZWxsLHRpbGVJRCxjZWxsSUQsc2VwID0gJy0nLHJlbW92ZSA9IEYpICU+JQogICAgICAgIG11dGF0ZSh0b3RhbEV4cCA9IHJvd1N1bXMoLnlbLC0oMTozKV0pKQogICAgICB9KSwKICAgICkgJT4lCiAgc2VsZWN0KC1wYXRoLC1tZXRyaWMpCmBgYAoKCiMjIFF1YWxpdHkgY2hlY2sgJiBjb250cm9sCgpgYGB7cn0KZGF0YSAlPiUKICBzZWxlY3QoLUlGKSAlPiUgdW5uZXN0KG1ldGEpICU+JQogIGdncGxvdChhZXMoeCx5LGNvbG9yPWRheSkpICsKICAgIHRoZW1lX3ZvaWQoKSArCiAgICBnZW9tX3BvaW50KHNpemU9LjUpICsgZGNvbG9yICsKICAgIGZhY2V0X2dyaWQocmVwfmRheSkgKyBjb29yZF9maXhlZCgpCmBgYAoKYGBge3J9CmRhdGEgJT4lCiAgc2VsZWN0KHNhbXBsZU5hbWUsbWV0YSkgJT4lIHVubmVzdChtZXRhKSAlPiUKICBnZ3Bsb3QoYWVzKHNhbXBsZU5hbWUsdG90YWxFeHApKSArIGdlb21fdmlvbGluKCkgKyBnZW9tX2JveHBsb3QoKQpgYGAKCmBgYHtyfQp0aHJzbCA9IGMoNyw3LDUpCm5hbWVzKHRocnNsKSA8LSBjKCdkMCcsJ2QzJywnZDYnKQoKZGF0YWYgPC0gZGF0YSAlPiUKICBtdXRhdGUoCiAgICBpZHggPSBtYXAyKG1ldGEsZGF5LH57LngkdG90YWxFeHAgPiB0aHJzbFsueV19KSwKICAgIElGID0gbWFwMihJRixpZHgsfnsueFsueSxdfSksCiAgICBtZXRhID0gbWFwMihtZXRhLGlkeCx+ey54Wy55LF19KQogICAgKSAlPiUKICBtdXRhdGUoCiAgICBpZHgyID0gbWFwKElGLH57cm93U3VtcygueFssLTFdPT0wKSA9PSAwfSksCiAgICBJRiA9IG1hcDIoSUYsaWR4Mix+ey54Wy55LF19KSwKICAgIG1ldGEgPSBtYXAyKG1ldGEsaWR4Mix+ey54Wy55LF19KSwKICAgIG5DZWxsID0gbWFwKGlkeDIsc3VtKSAlPiUgdW5saXN0KCkKICAgICkgJT4lCiAgc2VsZWN0KC1pZHgsLWlkeDIpCgpkYXRhZiAlPiUKICBzZWxlY3Qoc2FtcGxlTmFtZSxtZXRhKSAlPiUgdW5uZXN0KG1ldGEpICU+JQogIGdncGxvdChhZXMoc2FtcGxlTmFtZSx0b3RhbEV4cCkpICsgCiAgZ2VvbV92aW9saW4oKSArIGdlb21fYm94cGxvdCgpCmBgYAoKYGBge3J9CmRhdGFmICU+JSAgCiAgZ2dwbG90KGFlcyhzYW1wbGVOYW1lLG5DZWxsKSkgKyAKICB0aGVtZV9jbGFzc2ljKCkgKwogIGdlb21fYmFyKHN0YXQ9J2lkZW50aXR5JykgKwogIHNjYWxlX3hfZGlzY3JldGUobGltaXRzPXJldikgKwogIHRoZW1lKGF4aXMudGl0bGUueSA9IGVsZW1lbnRfYmxhbmsoKSkgKyAgCiAgY29vcmRfZmxpcCgpICsgdGhlbWUoYXNwZWN0LnJhdGlvID0gLjM1KSAKYGBgCgpgYGB7cn0KdG1wIDwtIGRhdGFmICU+JSBzZWxlY3Qoc2FtcGxlTmFtZSxJRikgJT4lIHVubmVzdChJRikKaWR4IDwtIHdpdGgodG1wLG1vZGVsLm1hdHJpeCh+MCtzYW1wbGVOYW1lKSkKY29sbmFtZXMoaWR4KSA8LSBzdWIoInNhbXBsZU5hbWUiLCIiLGNvbG5hbWVzKGlkeCkpCmF2Z21hdCA8LSB0bXBbLC0oMToyKV0gJT4lIGFzLm1hdHJpeCgpICU+JSB0ICU+JSB7dCguJSolaWR4KS9jb2xTdW1zKGlkeCl9ICU+JSBzY2FsZSAlPiUgYXNpbmggJT4lIHQgCnBoZWF0bWFwOjpwaGVhdG1hcChhdmdtYXQsY2x1c3Rlcl9jb2xzID0gRixjZWxsd2lkdGggPSAxMCxjb2xvcj12aXJpZGlzOjp2aXJpZGlzKDEwKSxzaG93X3Jvd25hbWVzID0gRikKYGBgCgpgYGB7cn0KTWV0YSA8LSBkYXRhZiAlPiUKICBzZWxlY3Qoc2FtcGxlTmFtZSxkYXkscmVwLG1ldGEpICU+JSB1bm5lc3QobWV0YSkgJT4lCiAgdW5pdGUoQ2VsbCxkYXkscmVwLGNlbGwsc2VwID0gJy0nLHJlbW92ZSA9IEYpClggPC0gYmluZF9yb3dzKGRhdGFmJElGKSAlPiUgc2VsZWN0KC1jZWxsKSAlPiUgCiAgIHNjYWxlKCkgJT4lIGFzaW5oKCkKcm93bmFtZXMoWCkgPC0gTWV0YSRDZWxsCmBgYAoKCmBgYHtyfQpwIDwtIHByY29tcChYKQpzY3JlZXBsb3QocCx0eXBlPSdsJyxuPTMwKQpgYGAKCmBgYHtyfQp0b3RhbCA8LSBiaW5kX3Jvd3MoZGF0YWYkbWV0YSkgJT4lIHB1bGwodG90YWxFeHApIApjb3IocCR4WywxOjMwXSx0b3RhbCkgJT4lCiAgcGxvdCh4bGFiID0gJ1BDJyx5bGFiID0gJ0NvckNvZWZfdnNUb3RhbEV4cCcpCmBgYAoKCmBgYHtyIGZpZy5hc3A9LjV9CnVtIDwtIHV3b3Q6OnVtYXAocCR4WywyOjIwXSwKICAgICAgICAgICAgICAgICBtZXRyaWMgPSAnY29zaW5lJywKICAgICAgICAgICAgICAgICBuX25laWdoYm9ycyA9IDMwLAogICAgICAgICAgICAgICAgIG1pbl9kaXN0ID0gLjMpICU+JQogIGFzX3RpYmJsZShyb3duYW1lcyA9ICdDZWxsJykgJT4lCiAgZHBseXI6OnJlbmFtZShVTUFQMT0yLFVNQVAyPTMpCk1ldGF1bSA8LSBpbm5lcl9qb2luKE1ldGEsdW0sYnkgPSAnQ2VsbCcpCnVtcyA8LSBsaXN0KAogIHN0eWxlKE1ldGF1bSwnZGF5Jyx0aXRsZSA9IE5VTEwsc2l6ZSA9IC4yNSxhbHBoYSA9IC42KSwKICBzdHlsZShNZXRhdW0sJ3JlcCcsdGl0bGUgPSBOQSxzaXplID0gLjI1LGFscGhhID0gLjYpLAogIHN0eWxlKE1ldGF1bSwndG90YWxFeHAnLHRpdGxlID0gTkEsc2l6ZSA9IC4yNSxhbHBoYSA9IC42KQogICkgJT4lCiAgcGF0Y2h3b3JrOjp3cmFwX3Bsb3RzKG5jb2wgPSAzKQp1bXMKYGBgCgpgYGB7ciBmaWcuYXNwPTAuNn0KbWFya2VycyA8LSBjKCdIMy4xJywnTE1OQjEnLCdJTC02JywncDIxJywncFNUQVQzJykKbWFya2Vyc19leHAgPC0gWCAlPiUKICBhc190aWJibGUocm93bmFtZXMgPSAnQ2VsbCcpICU+JQogIHNlbGVjdChDZWxsLGFsbF9vZihtYXJrZXJzKSkgJT4lCiAgZ2F0aGVyKGtleT1TWU1CT0wsdmFsdWU9RXhwTGV2ZWwsLUNlbGwpCmExIDwtIE1ldGF1bSAlPiUKICBpbm5lcl9qb2luKG1hcmtlcnNfZXhwLGJ5ID0gJ0NlbGwnKSAlPiUKICBzdHlsZSgnRXhwTGV2ZWwnLHNpemU9LjIsdGl0bGUgPSAiIikgKyAKICAgIHRoZW1lX2J3KCkgKyBmYWNldF93cmFwKH5TWU1CT0wsbnJvdz0xKSArCiAgICB0aGVtZShheGlzLnRpY2tzID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgYXhpcy50ZXh0ID0gZWxlbWVudF9ibGFuaygpKSArCiAgICBsYWJzKGNvbG9yPSdFeHByZXNzaW9uIExldmVsJykKYTIgPC0gTWV0YXVtICU+JQogIGlubmVyX2pvaW4obWFya2Vyc19leHAsYnk9J0NlbGwnKSAlPiUKICBnZ3Bsb3QoYWVzKHNhbXBsZU5hbWUsRXhwTGV2ZWwsZmlsbD1kYXkpKSArCiAgICB0aGVtZV9idygpICsKICAgIGdlb21fdmlvbGluKCkgKyAKICAgIGdlb21fYm94cGxvdCh3aWR0aD0uMixmaWxsPSd3aGl0ZScsb3V0bGllci5jb2xvciA9IE5BKSArCiAgICBmYWNldF93cmFwKH5TWU1CT0wsbnJvdz0xLHNjYWxlcz0nZnJlZScpICsKICAgIHRoZW1lKGF4aXMudGlja3MueCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF9ibGFuaygpKSArCiAgICBkZmlsbCArIGxhYnMoeT0nRXhwcmVzc2lvbiBMZXZlbCcpCihhMS9hMikgKyBwYXRjaHdvcms6OnBsb3RfbGF5b3V0KGd1aWRlcyA9ICdjb2xsZWN0JyxoZWlnaHRzID0gYygxLjMsMSkpCmBgYOOAgAoKCgoKIyMgUHNldWRvIHRpbWUKCmBgYHtyfQpTZW5lcyA8LSBjKCJnSDJBWCIsIkhNR0ExIiwiS2k2NyIsIklMLTFhIiwiSUwtNiIsIklMLTgiLCJMTU5CMSIsInAyMSIsInA1M0JQMSIsIlBNTCIpClNlbmVzX2V4cCA8LSBYICU+JQogIGFzX3RpYmJsZShyb3duYW1lcyA9ICdDZWxsJykgJT4lCiAgc2VsZWN0KENlbGwsYWxsX29mKFNlbmVzKSkgJT4lCiAgZ2F0aGVyKGtleT1TWU1CT0wsdmFsdWU9RXhwTGV2ZWwsLUNlbGwpCk1ldGF1bSAlPiUKICBpbm5lcl9qb2luKFNlbmVzX2V4cCxieT0nQ2VsbCcpICU+JQogIGdncGxvdChhZXMoc2FtcGxlTmFtZSxFeHBMZXZlbCxmaWxsPWRheSkpICsKICAgIHRoZW1lX2J3KCkgKwogICAgZ2VvbV92aW9saW4oKSArIAogICAgZ2VvbV9ib3hwbG90KHdpZHRoPS4yLGZpbGw9J3doaXRlJyxvdXRsaWVyLmNvbG9yID0gTkEpICsKICAgIGZhY2V0X3dyYXAoflNZTUJPTCxzY2FsZXM9J2ZyZWVfeScpICsKICAgIHRoZW1lKGF4aXMudGlja3MueCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF9ibGFuaygpKSArCiAgICBkZmlsbCArIGxhYnMoeT0nRXhwcmVzc2lvbiBMZXZlbCcpCmBgYAoKYGBge3J9CnAyIDwtIHByY29tcChYWyxTZW5lc10pCnNjcmVlcGxvdChwMix0eXBlPSdsJyxuPTMwKQpgYGAKCmBgYHtyfQpwaCA8LSBwMiR4WywtMV0gJT4lIAogIHBoYXRlUjo6cGhhdGUobWRzLmRpc3QubWV0aG9kID0gImNvc2luZSIsCiAgICAgICAgICAgICAgICBrbm4gPSAzMCkKTWV0YXVtIDwtIE1ldGF1bSAlPiUKICBtdXRhdGUoUEhBVEUxPXBoJGVtYmVkZGluZ1ssMV0sUEhBVEUyPXBoJGVtYmVkZGluZ1ssMl0pCgpzdHlsZShNZXRhdW0sImRheSIsbWV0aG9kID0gIlBIQVRFIixzaXplPS41KQpzdHlsZShNZXRhdW0sInRvdGFsRXhwIixtZXRob2QgPSAiUEhBVEUiLHNpemU9LjUpCmBgYAoKCmBgYHtyfQpzZXQuc2VlZCgxKTsgY2x1IDwtIHBoJGVtYmVkZGluZyAlPiUga21lYW5zKGNlbnRlcnMgPSA1KQpwaCRlbWJlZGRpbmcgJT4lCiAgYXNfdGliYmxlKCkgJT4lCiAgbXV0YXRlKGNsdT1hcy5jaGFyYWN0ZXIoY2x1JGNsdXN0ZXIpKSAlPiUKICBzdHlsZSgiY2x1IiwiUEhBVEUiLHBhbGV0dGUgPSBGLHNpemU9LjMpCmBgYAoKYGBge3J9CmxpYnJhcnkoc2xpbmdzaG90KQpzY2UgPC0gZ2V0TGluZWFnZXMocGgkZW1iZWRkaW5nLCBjbHUkY2x1c3Rlciwgc3RhcnQuY2x1cyA9ICczJykKc2NlIDwtIGdldEN1cnZlcyhzY2UpCnNjZQpQVCA8LSBzbGluZ1BzZXVkb3RpbWUoc2NlKVssMV0KUFQgPC0gKFBULW1pbihQVCkpLyhtYXgoUFQpLW1pbihQVCkpCk1ldGF1bSA8LSBNZXRhdW0gJT4lIG11dGF0ZShQc2V1ZG9UaW1lPVBUKQpNZXRhdW0KYGBgCgpgYGB7cn0Ke3N0eWxlKE1ldGF1bSwnUHNldWRvVGltZScsIlBIQVRFIixzaXplPS41KS9zdHlsZShNZXRhdW0sJ1BzZXVkb1RpbWUnLCJVTUFQIixzaXplPS41KX0gKyBwYXRjaHdvcms6OnBsb3RfbGF5b3V0KGd1aWRlcyA9ICdjb2xsZWN0Jyl8CiAge3N0eWxlKE1ldGF1bSwnZGF5JywiUEhBVEUiLHNpemU9LjUpL3N0eWxlKE1ldGF1bSwnZGF5JywiVU1BUCIsc2l6ZT0uNSl9ICsgcGF0Y2h3b3JrOjpwbG90X2xheW91dChndWlkZXMgPSAnY29sbGVjdCcpCmBgYAoKYGBge3J9Ck1ldGF1bSA8LSBNZXRhdW0gJT4lCiAgYXJyYW5nZShQc2V1ZG9UaW1lKSAlPiUKICBtdXRhdGUoCiAgICBkUHNldWRvVGltZSA9IChmbG9vcihQc2V1ZG9UaW1lKjEwKSsuNSkvMTAsCiAgICBkUHNldWRvVGltZSA9IGlmZWxzZShkUHNldWRvVGltZT09MS4wNSwuOTUsZFBzZXVkb1RpbWUpLAogICAgZFBzZXVkb1RpbWUgPSBhcy5mYWN0b3IoZFBzZXVkb1RpbWUpCiAgICApCgpNZXRhdW0gJT4lCiAgZ2dwbG90KGFlcyhQc2V1ZG9UaW1lLGZpbGw9ZGF5KSkgKwogIHRoZW1lX2J3KCkgKwogIGdlb21faGlzdG9ncmFtKHBvc2l0aW9uID0gImRvZGdlIikgKyBkZmlsbApNZXRhdW0gJT4lCiAgZHBseXI6OmNvdW50KGRQc2V1ZG9UaW1lLGRheSkgJT4lCiAgZ2dwbG90KGFlcyhkUHNldWRvVGltZSxuLGZpbGw9ZGF5KSkgKyBnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5IikgKyBkZmlsbCArCiAgdGhlbWVfYncoKSArCiAgbGFicyh5PSJjb3VudCIpCmBgYAoKYGBge3J9CmEgPC0gYXNfdGliYmxlKFgscm93bmFtZXM9IkNlbGwiKSAlPiUKICBnYXRoZXIoa2V5PVByb3QsdmFsdWU9RXhwTGV2ZWwsLUNlbGwpICU+JQogIGxlZnRfam9pbihNZXRhdW0sYnk9IkNlbGwiKQoKTmV3ZGF0YSA8LSBzZXEoMC4yLDAuOCxieT0wLjAxKQpTcGFuIDwtIDAuMwoKdXNlX3AxIDwtIGMoJ0hNR0ExJywnS2k2NycsJ0xNTkIxJywncDIxJywncDUzQlAxJywnUE1MJykKTE9FU1MxIDwtIGEgJT4lCiAgZmlsdGVyKFByb3QgJWluJSB1c2VfcDEpICU+JQogIHNlbGVjdChQcm90LEV4cExldmVsLFBzZXVkb1RpbWUpICU+JQogIGdyb3VwX2J5KFByb3QpICU+JSBuZXN0KCkgJT4lCiAgbXV0YXRlKExPRVNTID0gbWFwKGRhdGEsfmxvZXNzKEV4cExldmVsflBzZXVkb1RpbWUsLngsc3BhbiA9IFNwYW4pKSwKICAgICAgICAgUFJFRCA9IG1hcChMT0VTUyx+cHJlZGljdCgueCxuZXdkYXRhPU5ld2RhdGEpKSwKICAgICAgICAgbmV3ZGF0YSA9IG1hcChQUkVELH50aWJibGUoUHNldWRvVGltZT1OZXdkYXRhLFBSRUQ9LngpKSApCgpsb2Vzc1BsIDwtIGZ1bmN0aW9uKHgscmF0aW8gPSAuMzUpewogIGdncGxvdCh4LGFlcyhQc2V1ZG9UaW1lLHNjYWxlZCxjb2xvcj1Qcm90KSkgKyAKICBnZW9tX2xpbmUoKSArIHRoZW1lX2J3KCkgKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICdub25lJykgKwogIHRoZW1lKGF4aXMudGlja3MueSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBzdHJpcC5iYWNrZ3JvdW5kID0gZWxlbWVudF9yZWN0KGZpbGw9TkEsY29sb3I9TkEpLCAKICAgICAgICBwYW5lbC5ncmlkLm1ham9yLnggPSBlbGVtZW50X2JsYW5rKCkscGFuZWwuZ3JpZC5taW5vciA9IGVsZW1lbnRfYmxhbmsoKSkgKyAKICBjb29yZF9maXhlZChyYXRpbz1yYXRpbykgKyBsYWJzKHkgPSAnc2NhbGVkIGV4cHJlc3Npb24gbGV2ZWwnKQp9CgpMT0VTUzFfc2NhbGVkIDwtIExPRVNTMDEwZiAlPiUgc2VsZWN0KFByb3QsbmV3ZGF0YSkgJT4lIAogIG11dGF0ZShuZXdkYXRhID0gbWFwKG5ld2RhdGEsfm11dGF0ZSgueCxzY2FsZWQgPSAoUFJFRC1taW4oUFJFRCkpLyhtYXgoUFJFRCktbWluKFBSRUQpKSApKSkgJT4lCiAgdW5uZXN0KG5ld2RhdGEpICAlPiUgdW5ncm91cCgpCmxvZXNzUGwoTE9FU1MxX3NjYWxlZCkKbG9lc3NQbChMT0VTUzFfc2NhbGVkKSArIGZhY2V0X3dyYXAoflByb3QpCmBgYAoKYGBge3J9CnVzZV9wMiA8LSBjKCJiQ2F0ZW5pbiIsIkJDTDJMMSIsIkZyaXp6bGVkNyIsIk5PVENIMSIsInA1M0JQMSIsInBBS1QiLCJwQVRGMiIsInBBVE0iLCJwU01BRDEvNSIsInBTTUFEMyIsInBTUkMiKQoKTE9FU1MyIDwtIGEgJT4lCiAgZmlsdGVyKFByb3QgJWluJSB1c2VfcDIpICU+JQogIHNlbGVjdChQcm90LEV4cExldmVsLFBzZXVkb1RpbWUpICU+JQogIGdyb3VwX2J5KFByb3QpICU+JSBuZXN0KCkgJT4lCiAgbXV0YXRlKExPRVNTID0gbWFwKGRhdGEsfmxvZXNzKEV4cExldmVsflBzZXVkb1RpbWUsLngsc3BhbiA9IFNwYW4pKSwKICAgICAgICAgUFJFRCA9IG1hcChMT0VTUyx+cHJlZGljdCgueCxuZXdkYXRhPU5ld2RhdGEpKSwKICAgICAgICAgbmV3ZGF0YSA9IG1hcChQUkVELH50aWJibGUoUHNldWRvVGltZT1OZXdkYXRhLFBSRUQ9LngpKSApCkxPRVNTMl9zY2FsZWQgPC0gTE9FU1MyICU+JSBzZWxlY3QoUHJvdCxuZXdkYXRhKSAlPiUgCiAgbXV0YXRlKG5ld2RhdGEgPSBtYXAobmV3ZGF0YSx+bXV0YXRlKC54LHNjYWxlZCA9IChQUkVELW1pbihQUkVEKSkvKG1heChQUkVEKS1taW4oUFJFRCkpICkpKSAlPiUKICB1bm5lc3QobmV3ZGF0YSkgICU+JSB1bmdyb3VwKCkKbG9lc3NQbChMT0VTUzJfc2NhbGVkKQpsb2Vzc1BsKExPRVNTMl9zY2FsZWQpICsgZmFjZXRfd3JhcCh+UHJvdCkKYGBgCgpgYGB7cn0KTE9FU1MzIDwtIGEgJT4lCiAgZmlsdGVyKCFQcm90ICVpbiUgdXNlX3AxLAogICAgICAgICAhUHJvdCAlaW4lIHVzZV9wMikgJT4lCiAgc2VsZWN0KFByb3QsRXhwTGV2ZWwsUHNldWRvVGltZSkgJT4lCiAgZ3JvdXBfYnkoUHJvdCkgJT4lIG5lc3QoKSAlPiUKICBtdXRhdGUoTE9FU1MgPSBtYXAoZGF0YSx+bG9lc3MoRXhwTGV2ZWx+UHNldWRvVGltZSwueCxzcGFuID0gU3BhbikpLAogICAgICAgICBQUkVEID0gbWFwKExPRVNTLH5wcmVkaWN0KC54LG5ld2RhdGE9TmV3ZGF0YSkpLAogICAgICAgICBuZXdkYXRhID0gbWFwKFBSRUQsfnRpYmJsZShQc2V1ZG9UaW1lPU5ld2RhdGEsUFJFRD0ueCkpICkKTE9FU1MzX3NjYWxlZCA8LSBMT0VTUzMgJT4lIHNlbGVjdChQcm90LG5ld2RhdGEpICU+JSAKICBtdXRhdGUobmV3ZGF0YSA9IG1hcChuZXdkYXRhLH5tdXRhdGUoLngsc2NhbGVkID0gKFBSRUQtbWluKFBSRUQpKS8obWF4KFBSRUQpLW1pbihQUkVEKSkgKSkpICU+JQogIHVubmVzdChuZXdkYXRhKSAgJT4lIHVuZ3JvdXAoKQppZCA8LSB1bmlxdWUoTE9FU1MzX3NjYWxlZCRQcm90KQpMT0VTUzNfc2NhbGVkICU+JQogIGZpbHRlcihQcm90ICVpbiUgaWRbMTo0OF0pICU+JQogIGxvZXNzUGwocmF0aW8gPSAuMikgKyBmYWNldF93cmFwKH5Qcm90LG5yb3cgPSA2KSArCiAgdGhlbWUoYXhpcy50ZXh0ID0gZWxlbWVudF9ibGFuaygpLGF4aXMudGlja3MueCA9IGVsZW1lbnRfYmxhbmsoKSkKTE9FU1MzX3NjYWxlZCAlPiUKICBmaWx0ZXIoUHJvdCAlaW4lIGlkWzQ5Ojk2XSkgJT4lCiAgbG9lc3NQbChyYXRpbyA9IC4yKSArIGZhY2V0X3dyYXAoflByb3QsbnJvdyA9IDYpICsKICB0aGVtZShheGlzLnRleHQgPSBlbGVtZW50X2JsYW5rKCksYXhpcy50aWNrcy54ID0gZWxlbWVudF9ibGFuaygpKQpMT0VTUzNfc2NhbGVkICU+JQogIGZpbHRlcihQcm90ICVpbiUgaWRbOTc6MTQ0XSkgJT4lCiAgbG9lc3NQbChyYXRpbyA9IC4yKSArIGZhY2V0X3dyYXAoflByb3QsbnJvdyA9IDYpICsgCiAgdGhlbWUoYXhpcy50ZXh0ID0gZWxlbWVudF9ibGFuaygpLGF4aXMudGlja3MueCA9IGVsZW1lbnRfYmxhbmsoKSkKTE9FU1MzX3NjYWxlZCAlPiUKICBmaWx0ZXIoUHJvdCAlaW4lIGlkWzE0NToxOTBdKSAlPiUKICBsb2Vzc1BsKHJhdGlvID0gLjMpICsgZmFjZXRfd3JhcCh+UHJvdCxucm93ID0gNikgKwogIHRoZW1lKGF4aXMudGV4dCA9IGVsZW1lbnRfYmxhbmsoKSxheGlzLnRpY2tzLnggPSBlbGVtZW50X2JsYW5rKCkpCmBgYAoKCiMjIFNwYXRpYWwKCiMjIyBzcGF0aWFsIGNvcnJyZWxhdGlvbgoKYGBge3J9CnNwYUNvciA8LSBmdW5jdGlvbiAoeCwgY29vcmQsIHNjYWxlZCA9IEZBTFNFLCBSID0gOTk5KXsKICB4IDwtIGFzaW5oKHNjYWxlKHgpKQogIHBvcy5kaXN0IDwtIGRpc3QoeCA9IGNvb3JkKQogIHBvcy5kaXN0Lm1hdCA8LSBhcy5tYXRyaXgoeCA9IHBvcy5kaXN0KQogIHcgPC0gMS9wb3MuZGlzdC5tYXReMgogIGRpYWcoeCA9IHcpIDwtIDAKICB3IDwtIHcvc3VtKHcpCiAgWCA8LSB0KHcpICUqJSBzY2FsZSh4KQogIHQoc2NhbGUoeCkpJSolWAp9CgpTcGFzIDwtIGRhdGFmICU+JQogIHNlbGVjdChzYW1wbGVOYW1lLGRheSxyZXAsbWV0YSxJRikgJT4lCiAgbXV0YXRlKAogICAgSUYgPSBtYXAyKElGLHNhbXBsZU5hbWUsfnsKICAgICAgLnggJT4lIAogICAgICAgIG11dGF0ZShjZWxsID0gcGFzdGUoLnksY2VsbCxzZXA9Jy0nKSkgJT4lCiAgICAgICAgdGliMmRmKCkgJT4lIGFzLm1hdHJpeCgpIH0pLAogICAgbWV0YSA9IG1hcDIobWV0YSxzYW1wbGVOYW1lLH57CiAgICAgIC54ICU+JQogICAgICAgIG11dGF0ZShDZWxsID0gcGFzdGUoLnksY2VsbCxzZXA9Jy0nKSkgJT4lIAogICAgICAgIHNlbGVjdChDZWxsLHgseSkgJT4lIHRpYjJkZigpICU+JSBhcy5tYXRyaXgoKX0pLAogICAgc3BhdGlhbENvck1hdCA9IG1hcDIoSUYsbWV0YSx+c3BhQ29yKC54LC55LHNjYWxlZCA9IFQpKSwKICAgIHNwYXRpYWxDb3JMaXN0ID0gbWFwKHNwYXRpYWxDb3JNYXQsfnsKICAgICAgLnhbIXVwcGVyLnRyaSgueCldIDwtIE5BCiAgICAgIGFzX3RpYmJsZSgueCxyb3duYW1lcyA9ICdBJykgJT4lCiAgICAgICAgZ2F0aGVyKEIsc3BhdGlhbENvciwtQSkgJT4lIG5hLm9taXQoKQogICAgICB9KSwKICAgIGNhbm9uaWNhbENvck1hdCA9IG1hcDIoSUYsbWV0YSx+Y29yKC54KSksCiAgICBjYW5vbmljYWxDb3JMaXN0ID0gbWFwKGNhbm9uaWNhbENvck1hdCx+ewogICAgICAueFshdXBwZXIudHJpKC54KV0gPC0gTkEKICAgICAgYXNfdGliYmxlKC54LHJvd25hbWVzID0gJ0EnKSAlPiUKICAgICAgICBnYXRoZXIoQixjYW5vbmljYWxDb3IsLUEpICU+JSBuYS5vbWl0KCkKICAgICAgfSkKICAgICkgJT4lCiAgc2VsZWN0KC1JRiwtbWV0YSkgJT4lCiAgbXV0YXRlKGRhdCA9IG1hcDIoc3BhdGlhbENvckxpc3QsY2Fub25pY2FsQ29yTGlzdCx+ewogICAgaW5uZXJfam9pbigueCwueSxieT1jKCdBJywnQicpKQogIH0pKSAKU3Bhc18gPC0gU3BhcyAlPiUKICBzZWxlY3QoLXNwYXRpYWxDb3JNYXQpICU+JQogIHVubmVzdChzcGF0aWFsQ29yTGlzdCkgJT4lCiAgdW5ncm91cCgpICU+JQogIHVuaXRlKGxhYixBLEIsc2VwPSc6JykKYGBgCgpgYGB7cn0KdXNlIDwtIGMoIk5PVENIMTpwQVRNIiwicDIxOk5PVENIMSIsInAyMTpwQVRNIikKU3Bhc18gJT4lIGZpbHRlcihsYWIgJWluJSB1c2UpICU+JQogIGdyb3VwX2J5KGRheSxsYWIgKSAlPiUgc3VtbWFyaXNlKHNwYXRpYWxDb3IgPSBtZWFuKHNwYXRpYWxDb3IpKSAlPiUKICB1bmdyb3VwKCkgJT4lCiAgZ2dwbG90KGFlcyhkYXksc3BhdGlhbENvcixjb2xvcj1sYWIsZ3JvdXA9bGFiKSkgKyAKICB0aGVtZV9idygpICsKICBnZW9tX3BvaW50KHNpemU9MikgKyAKICBnZW9tX2xpbmUoKSArCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMCxsaW5ldHlwZT0nZGFzaGVkJykgKwogIHRoZW1lKHBhbmVsLmdyaWQubWFqb3IueCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBwYW5lbC5ncmlkLm1pbm9yLnggPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50aXRsZS54ID0gZWxlbWVudF9ibGFuaygpKSArCiAgbGFicyhjb2xvciA9ICdQYWlyJyx5ID0gJ3NwYXRpYWwgY29ycmVsYXRpb24gY29lZi4nKQpgYGAKCgojIyMgc3BhdGlhbCBkaXN0cmlidXRpb24gb2YgZXhwcmVzc2lvbgoKYGBge3J9CnNtb290aFRpbGUgPC0gZnVuY3Rpb24oQ29vcmQsWCxOPTMsZ3JpZD01MCxidz0yKnNxcnQoMiksZGY9RkFMU0UsY2VudGVyX3dlaWdodD0zKSB7CiAgdXNlIDwtIENvb3JkICU+JSBhc190aWJibGUoKSAlPiUgbXV0YXRlKGV4cCA9IFgpCiAgdGVtcGxhdGUgPC0gZXhwYW5kX2dyaWQoCiAgICB0aWJibGUoeCA9IHNlcSgxOmdyaWQpKSwKICAgIHRpYmJsZSh5ID0gc2VxKDE6Z3JpZCkpCiAgICApCiAgRCA8LSBhcy5tYXRyaXgoZGlzdCh0ZW1wbGF0ZSkpCiAgRCA8LSBEPD1idwogIHRtcCA8LSB1c2UgJT4lCiAgICBtdXRhdGUoCiAgICAgIHggPSBmbG9vcihncmlkKngvMjA0ODApLAogICAgICB5ID0gZmxvb3IoZ3JpZCp5LzIwNDgwKQogICAgICApICU+JQogICAgZ3JvdXBfYnkoeCx5KSAlPiUKICAgIHN1bW1hcmlzZShleHA9bWVhbihleHApKSAlPiUgdW5ncm91cCgpICU+JQogICAgbGVmdF9qb2luKHRlbXBsYXRlLC4sYnk9YygieCIsInkiKSkKICBpZihkZil7CiAgICByZXR1cm4odG1wKQogIH1lbHNlewogICAgZm9yIChqIGluIDE6TikgewogICAgICB0bXAkZXhwIDwtIGxhcHBseSgxOm5yb3codGVtcGxhdGUpLCBmdW5jdGlvbih4KXsKICAgICAgICBpZighaXMubmEodG1wJGV4cFt4XSkpIHRtcCRleHBbeF0gPC0gY2VudGVyX3dlaWdodCp0bXAkZXhwW3hdCiAgICAgICAgaWR4IDwtIHdoaWNoKERbeCxdKQogICAgICAgIGV4cHMgPC0gdG1wJGV4cFtpZHhdICU+JSBuYS5vbWl0CiAgICAgICAgaWYoIWlzLm5hKHRtcCRleHBbeF0pKSB7CiAgICAgICAgICByZXMgPC0gc3VtKGV4cHMpLyhsZW5ndGgoZXhwcykrY2VudGVyX3dlaWdodC0xKQogICAgICAgIH1lbHNlewogICAgICAgICAgcmVzIDwtIG1lYW4oZXhwcykKICAgICAgICB9CiAgICAgICAgcmV0dXJuKHJlcykKICAgICAgICB9KSAlPiUKICAgICAgICB1bmxpc3QoKQogICAgICB0bXAkZXhwIDwtIGlmZWxzZShpcy5uYSh0bXAkZXhwKSxtaW4odG1wJGV4cCxuYS5ybSA9IFQpLHRtcCRleHApCiAgICB9CiAgcmV0dXJuKHRtcCkKICB9Cn0KCmV4cHMxIDwtIFhbZ3JlcCgnZDMtcjEnLHJvd25hbWVzKFgpKSxdCmNvb3JkMSA8LSBNZXRhdW0gJT4lIGZpbHRlcihkYXk9PSdkMycscmVwPT0ncjEnKSAlPiUgc2VsZWN0KENlbGwseCx5KSAlPiUgdGliMmRmKCkKcDIxIDwtIHNtb290aFRpbGUoY29vcmQxLGV4cHMxW3Jvd25hbWVzKGNvb3JkMSksJ3AyMSddLE4gPSAzLGNlbnRlcl93ZWlnaHQgPSAzLGJ3ID0gMipzcXJ0KDIpLGdyaWQgPSA1MCkKcGF0bSA8LSBzbW9vdGhUaWxlKGNvb3JkMSxleHBzMVtyb3duYW1lcyhjb29yZDEpLCdwQVRNJ10sTiA9IDMsY2VudGVyX3dlaWdodCA9IDMsYncgPSAyKnNxcnQoMiksZ3JpZCA9IDUwKQpub3RjaCA8LSBzbW9vdGhUaWxlKGNvb3JkMSxleHBzMVtyb3duYW1lcyhjb29yZDEpLCdOT1RDSDEnXSxOID0gMyxjZW50ZXJfd2VpZ2h0ID0gMyxidyA9IDIqc3FydCgyKSxncmlkID0gNTApICAKYGBgCgpgYGB7cn0KcDIxcGF0bSA8LSBpbm5lcl9qb2luKAogIHAyMSAlPiUgZHBseXI6OnJlbmFtZShwMjEgPSBleHApLAogIHBhdG0gJT4lIGRwbHlyOjpyZW5hbWUocEFUTSA9IGV4cCksCiAgYnkgPSBjKCJ4IiwieSIpCiAgKSAlPiUKICBtdXRhdGUoCiAgICBwMjEgPSAocDIxLW1pbihwMjEpKS9tYXgocDIxLW1pbihwMjEpKSwKICAgIHBBVE0gPSAocEFUTS1taW4ocEFUTSkpL21heChwQVRNLW1pbihwQVRNKSksCiAgICBsb2cyRkMgPSBsb2cyKHAyMS9wQVRNKQogICAgKQoKcDIxXyA8LSBwMjEgJT4lCiAgZ2dwbG90KGFlcyh4LHksZmlsbCA9IHNjYWxlKGV4cCkpKSArIGdlb21fdGlsZSgpICsgY29vcmRfZml4ZWQoKSArIAogIHNjYWxlX2ZpbGxfZ3JhZGllbnQyKGxvdz0nd2hpdGUnLG1pZD0nd2hpdGUnLGhpZ2g9J2dyZWVuJykgKwogIHRoZW1lX3ZvaWQoKSArIGxhYnMoZmlsbD0ncDIxJykKcGF0bV8gPC0gcGF0bSAlPiUKICBnZ3Bsb3QoYWVzKHgseSxmaWxsID0gc2NhbGUoZXhwKSkpICsgZ2VvbV90aWxlKCkgKyBjb29yZF9maXhlZCgpICsgCiAgc2NhbGVfZmlsbF9ncmFkaWVudDIobG93PSd3aGl0ZScsbWlkPSd3aGl0ZScsaGlnaD0nYmx1ZScpKwogIHRoZW1lX3ZvaWQoKSArIGxhYnMoZmlsbD0ncEFUTScpCnAyMXBhdG1fIDwtIHAyMXBhdG0gJT4lCiAgbXV0YXRlKAogICAgbG9nMkZDID0gaWZlbHNlKGxvZzJGQyA8IC0yLjUsLTIuNSxsb2cyRkMpLAogICAgbG9nMkZDID0gaWZlbHNlKGxvZzJGQyA+IDIuNSwyLjUsbG9nMkZDKSkgJT4lCiAgZ2dwbG90KGFlcyh4LHksZmlsbCA9IGxvZzJGQykpICsgZ2VvbV90aWxlKCkgKyBjb29yZF9maXhlZCgpICsgCiAgc2NhbGVfZmlsbF9ncmFkaWVudDIobG93PSdibHVlJyxtaWQ9J3doaXRlJyxoaWdoPSdncmVlbicsbWlkcG9pbnQgPSAwKSArCiAgdGhlbWVfdm9pZCgpCnt7cDIxXy9wYXRtX318cDIxcGF0bV99ICsgcGF0Y2h3b3JrOjpwbG90X2xheW91dChndWlkZXM9J2NvbGxlY3QnKSAmIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICdib3R0b20nKQpgYGAKCgpgYGB7cn0KcDIxbm90Y2ggPC0gaW5uZXJfam9pbigKICBub3RjaCAlPiUgZHBseXI6OnJlbmFtZShOT1RDSDEgPSBleHApLAogIHBhdG0gJT4lIGRwbHlyOjpyZW5hbWUocEFUTSA9IGV4cCksCiAgYnkgPSBjKCJ4IiwieSIpCiAgKSAlPiUKICBtdXRhdGUoCiAgICBOT1RDSDEgPSAoTk9UQ0gxLW1pbihOT1RDSDEpKS9tYXgoTk9UQ0gxLW1pbihOT1RDSDEpKSwKICAgIHBBVE0gPSAocEFUTS1taW4ocEFUTSkpL21heChwQVRNLW1pbihwQVRNKSksCiAgICBsb2cyRkMgPSBsb2cyKE5PVENIMS9wQVRNKQogICAgKQoKbm90Y2hfIDwtIG5vdGNoICU+JQogIGdncGxvdChhZXMoeCx5LGZpbGwgPSBzY2FsZShleHApKSkgKyBnZW9tX3RpbGUoKSArIGNvb3JkX2ZpeGVkKCkgKyAKICBzY2FsZV9maWxsX2dyYWRpZW50Mihsb3c9J3doaXRlJyxtaWQ9J3doaXRlJyxoaWdoPSdyZWQnKSArCiAgdGhlbWVfdm9pZCgpICsgCiAgbGFicyhmaWxsPSdOT1RDSDEnKQpwMjFub3RjaF8gPC0gcDIxbm90Y2ggJT4lCiAgbXV0YXRlKAogICAgbG9nMkZDID0gaWZlbHNlKGxvZzJGQyA8IC0yLjUsLTIuNSxsb2cyRkMpLAogICAgbG9nMkZDID0gaWZlbHNlKGxvZzJGQyA+IDIuNSwyLjUsbG9nMkZDKSkgJT4lCiAgZ2dwbG90KGFlcyh4LHksZmlsbCA9IGxvZzJGQykpICsgZ2VvbV90aWxlKCkgKyBjb29yZF9maXhlZCgpICsgc2NhbGVfZmlsbF9ncmFkaWVudDIobG93PSdibHVlJyxtaWQ9J3doaXRlJyxoaWdoPSdyZWQnLG1pZHBvaW50ID0gMCkgKyB0aGVtZV92b2lkKCkKe3tub3RjaF8vcGF0bV99fHAyMW5vdGNoX30gKyBwYXRjaHdvcms6OnBsb3RfbGF5b3V0KGd1aWRlcz0nY29sbGVjdCcpICYgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gJ2JvdHRvbScpCmBgYAoKYGBge3J9CnNlc3Npb25JbmZvKCkKYGBgCgoK